package test.threads.queue.unstressed.impl;

import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;

import sun.misc.Unsafe;
import test.helpers.UnsafeHelper;

import static com.google.common.base.Preconditions.checkArgument;

/**
 * @author cheremin
 * @since 27.09.11,  14:58
 */
public abstract class AbstractSESDSequencer {
    /**
     * используем Updater.lazySet вместо volatile store при обновлении
     * курсоров
     */
    private static final boolean USE_LAZY_SET = true;
    private static final boolean USE_UNSAFE = false;
    /**
     * оптимизируем spin-wait loop-ы запоминая в локальные (не-волатильные)
     * переменные последние наблюдаемые значения барьеров. Тем самым мы экономим
     * на волатильном чтении
     */
    private static final boolean OPTIMIZE_SPIN_WAIT = true;

    //======================================================
    private static final AtomicLongFieldUpdater<AbstractSESDSequencer> tailUpdater = AtomicLongFieldUpdater.newUpdater( AbstractSESDSequencer.class, "tailCursor" );
    private static final AtomicLongFieldUpdater<AbstractSESDSequencer> headUpdater = AtomicLongFieldUpdater.newUpdater( AbstractSESDSequencer.class, "headCursor" );
    //======================================================

    private static final Unsafe unsafe;
    private static final long headCursorOffset;
    private static final long tailCursorOffset;

    static {
        unsafe = UnsafeHelper.unsafe();
        final Class<AbstractSESDSequencer> clazz = AbstractSESDSequencer.class;
        try {
            final Field headCursorField = clazz.getDeclaredField( "headCursor" );
            final Field tailCursorField = clazz.getDeclaredField( "tailCursor" );
            headCursorOffset = unsafe.objectFieldOffset( headCursorField );
            tailCursorOffset = unsafe.objectFieldOffset( tailCursorField );
        } catch ( NoSuchFieldException e ) {
            throw new RuntimeException( e );
        }
        System.out.printf(
                "%s: useLazySet: %b, optimizeSpinWait: %b, useUnsafe: %b\n",
                AbstractSESDSequencer.class.getSimpleName(),
                USE_LAZY_SET,
                OPTIMIZE_SPIN_WAIT,
                USE_UNSAFE
        );
    }

    /* Elements range: [headCursor, tailCursor)
    *
    * (tailCursor - headCursor) == elements count
    *
    * 0 <= (tailCursor - headCursor) <= length  => state invariant
    *
    * tailCursor - headCursor == length         => queue is full
    * tailCursor - headCursor == 0              => queue is empty
    *
    * (headCursor % size ) is the index of first item in queue
    * (tailCursor % size ) is the index of _cell_ for _next last item_
    */
    private final int length;

    private volatile long $pad11, $pad12, $pad13, $pad14, $pad15, $pad16, $pad17, $pad18 = 7;
    private volatile long headCursor = 0;
    private volatile long $pad21, $pad22, $pad23, $pad24, $pad25, $pad26, $pad27, $pad28 = 8;
    private volatile long tailCursor = 0;

    private volatile long $pad31, $pad32, $pad33, $pad34, $pad35, $pad36, $pad37, $pad38 = 9;
    private long lastHeadObserved = 0;
    private volatile long $pad41, $pad42, $pad43, $pad44, $pad45, $pad46, $pad47, $pad48 = 9;
    private long lastTailObserved = 0;
    private volatile long $pad51, $pad52, $pad53, $pad54, $pad55, $pad56, $pad57, $pad58 = 9;


    public long sumPaddingToPreventOptimisation() {
        try {
            long sum = 0;
            for ( final Field field : getClass().getFields() ) {
                if ( field.getName().startsWith( "$pad" ) && field.getType() == long.class ) {
                    final Number value = ( Number ) field.get( this );
                    sum += value.longValue();
                }
            }
            return sum;
        } catch ( IllegalAccessException e ) {
            return 0;
        }
    }


    public AbstractSESDSequencer( final int length ) {
        checkArgument( length > 0, "length(%s) must be >0", length );
        this.length = length;
        sumPaddingToPreventOptimisation();
    }

    protected long nextTail() {
        final long tail = tailCursor;
        waitWhileNotFull( tail );

//        assert ( tail == tailCursor ) : "tail=" + tail + " <> tailCursor=" + tailCursor + " -- seems like multi-threaded enqueue() detected!";
        return tail + 1;
    }

    protected void waitWhileNotFull( final long tail ) {
        //spin-wait: "while not full"
        final long target = tail - length;
        if ( OPTIMIZE_SPIN_WAIT ) {
            while ( target == lastHeadObserved ) {
                lastHeadObserved = headCursor;
            }
        } else {
            while ( target == headCursor ) {
            }
        }
    }


    protected long nextHead() {
        final long head = headCursor;
        waitWhileNotEmpty( head );
//        assert ( head == headCursor ) : "head=" + head + " <> headCursor=" + headCursor + " -- seems like multi-threaded dequeue() detected!";
        return head + 1;
    }

    protected void waitWhileNotEmpty( final long head ) {
        //spin-wait: "while not empty"
        if ( OPTIMIZE_SPIN_WAIT ) {
            while ( head == lastTailObserved ) {
                lastTailObserved = tailCursor;
            }
        } else {
            while ( head == tailCursor ) {
            }
        }
    }


    protected void publishTail( final long newTail ) {
        setTailCursor( newTail );
    }

    protected void publishHead( final long newHead ) {
        setHeadCursor( newHead );
    }

    public final int size() {
        return ( int ) ( tailCursor - headCursor );
    }

    private void setTailCursor( final long newTail ) {
//        assert ( newTail >= headCursor ) : "newTail(" + newTail + ") < headCursor(" + headCursor + ")";
        if ( USE_LAZY_SET ) {
            if ( USE_UNSAFE ) {
                unsafe.putOrderedLong( this, tailCursorOffset, newTail );
            } else {
                tailUpdater.lazySet( this, newTail );
            }
        } else {
            tailCursor = newTail;
        }
    }

    private void setHeadCursor( final long newHead ) {
//        assert ( newHead <= tailCursor ) : "newHead(" + newHead + ") > tailCursor(" + tailCursor + ")";
        if ( USE_LAZY_SET ) {
            if ( USE_UNSAFE ) {
                unsafe.putOrderedLong( this, headCursorOffset, newHead );
            } else {
                headUpdater.lazySet( this, newHead );
            }
        } else {
            headCursor = newHead;
        }
    }

    protected final int length() {
        return length;
    }
}
